using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Phoenix.Xna.Framework.Graphics.Lights;

namespace Phoenix.Xna.Framework.Graphics.Models
{
    public class SkinnedModelBasicEffect : Effect
    {
        public static int MaxSupportedBones = 80;
        public static int MaxSupportedLights = 8;
        private Matrix viewMatrix;
        private Matrix projectionMatrix;
        private bool lightEnabled;
        private bool normalMapEnabled;
        private EffectParameter bonesParam;
        private EffectParameter worldParam;
        private EffectParameter viewInverseParam;
        private EffectParameter viewProjectionParam;
        private EffectParameter diffuseMapEnabledParam;
        private EffectParameter specularMapEnabledParam;
        private Material material;
        private EffectParameter diffuseMapParam;
        private EffectParameter normalMapParam;
        private EffectParameter specularMapParam;
        private EffectParameter ambientLightColorParam;
        private EnabledLights enabledLights;
        private List<PointLight> pointLightCollection;

        public Matrix World
        {
            get { return worldParam.GetValueMatrix(); }
            set { worldParam.SetValue(value); }
        }
        public Matrix[] Bones
        {
            get { return bonesParam.GetValueMatrixArray(MaxSupportedBones); }
            set { bonesParam.SetValue(value); }
        }
        public Matrix View
        {
            get { return viewMatrix; }
            set
            {
                viewMatrix = value;
                viewInverseParam.SetValue(Matrix.Invert(value));

                viewProjectionParam.SetValue(viewMatrix * projectionMatrix);
            }
        }
        public Matrix Projection
        {
            get { return projectionMatrix; }
            set
            {
                projectionMatrix = value;
                viewProjectionParam.SetValue(viewMatrix * projectionMatrix);
            }
        }
        public bool DiffuseMapEnabled
        {
            get { return diffuseMapEnabledParam.GetValueBoolean(); }
            set { diffuseMapEnabledParam.SetValue(value); }
        }
        public Texture2D DiffuseMap
        {
            get { return diffuseMapParam.GetValueTexture2D(); }
            set { diffuseMapParam.SetValue(value); }
        }
        public bool SpecularMapEnabled
        {
            get { return specularMapEnabledParam.GetValueBoolean(); }
            set { specularMapEnabledParam.SetValue(value); }
        }
        public Texture2D SpecularMap
        {
            get { return specularMapParam.GetValueTexture2D(); }
            set { specularMapParam.SetValue(value); }
        }
        public bool NormalMapEnabled
        {
            get { return normalMapEnabled; }
            set
            {
                normalMapEnabled = value;
                UpdateTechnique();
            }
        }
        public Texture2D NormalMap
        {
            get { return normalMapParam.GetValueTexture2D(); }
            set { normalMapParam.SetValue(value); }
        }
        public bool LightEnabled
        {
            get { return lightEnabled; }
            set
            {
                lightEnabled = value;
                UpdateTechnique();
            }
        }
        public EnabledLights EnabledLights
        {
            get { return enabledLights; }
            set
            {
                enabledLights = value;
                UpdateTechnique();
            }
        }
        public Material Material
        {
            get { return material; }
        }
        public Vector3 AmbientLightColor
        {
            get { return ambientLightColorParam.GetValueVector3(); }
            set { ambientLightColorParam.SetValue(value); }
        }
        public List<PointLight> PointLights
        {
            get { return pointLightCollection; }
        }
        public ShaderProfile VertexShaderProfile
        {
            get
            {
                return GetShaderProfile(CurrentTechnique.Annotations["vertexShaderProfile"].GetValueString());
            }
        }
        public ShaderProfile PixelShaderProfile
        {
            get
            {
                return GetShaderProfile(CurrentTechnique.Annotations["pixelShaderProfile"].GetValueString());
            }
        }

        protected SkinnedModelBasicEffect(GraphicsDevice graphicsDevice, SkinnedModelBasicEffect clone)
            : base(graphicsDevice, clone)
        {
            CacheEffectParams();
        }

        public SkinnedModelBasicEffect(GraphicsDevice graphicsDevice, EffectPool effectPool)
            : base(graphicsDevice, SkinnedModelBasicEffectCode.Code, CompilerOptions.None, effectPool)
        {
            CacheEffectParams();
            InitializeEffectParams();
        }

        private void CacheEffectParams()
        {
            worldParam = Parameters["matW"];
            viewInverseParam = Parameters["matVI"];
            viewProjectionParam = Parameters["matVP"];
            bonesParam = Parameters["matBones"];
            diffuseMapEnabledParam = Parameters["diffuseMapEnabled"];
            specularMapEnabledParam = Parameters["specularMapEnabled"];
            EffectParameter materialStructParam = Parameters["material"];
            material = new Material(materialStructParam);
            diffuseMapParam = Parameters["diffuseMap0"];
            normalMapParam = Parameters["normalMap0"];
            specularMapParam = Parameters["specularMap0"];
            ambientLightColorParam = Parameters["ambientLightColor"];
            EffectParameter pointLightStructParam = Parameters["lights"];
            pointLightCollection = new List<PointLight>(MaxSupportedLights);

            for (int i = 0; i < MaxSupportedLights; i++)
            {
                pointLightCollection.Add(new PointLight(pointLightStructParam.Elements[i]));
            }
        }

        private void InitializeEffectParams()
        {
            World = Matrix.Identity;
            View = Matrix.Identity;
            Projection = Matrix.Identity;

            for (int i = 0; i < MaxSupportedBones; i++)
            {
                bonesParam.Elements[i].SetValue(Matrix.Identity);
            }

            LightEnabled = false;
            EnabledLights = EnabledLights.One;
            DiffuseMapEnabled = false;
            NormalMapEnabled = false;
            SpecularMapEnabled = false;
            Material.EmissiveColor = Vector3.Zero;
            Material.DiffuseColor = Vector3.One;
            Material.SpecularColor = Vector3.One;
            Material.SpecularPower = 16;

            for (int i = 0; i < MaxSupportedLights; i++)
            {
                PointLights[i].Position = Vector3.Zero;
                PointLights[i].Color = Vector3.Zero;
            }
        }

        private void UpdateTechnique()
        {
            if (lightEnabled)
            {
                switch (enabledLights)
                {
                    case EnabledLights.One:
                        CurrentTechnique = (normalMapEnabled)
                            ? Techniques["AnimatedModel_OneLightWithNormal"]
                            : Techniques["AnimatedModel_OneLight"];
                        break;
                    case EnabledLights.Two:
                        CurrentTechnique = (normalMapEnabled)
                            ? Techniques["AnimatedModel_TwoLightWithNormal"]
                            : Techniques["AnimatedModel_TwoLight"];
                        break;
                    case EnabledLights.Four:
                        CurrentTechnique = (normalMapEnabled)
                            ? Techniques["AnimatedModel_FourLightWithNormal"]
                            : Techniques["AnimatedModel_FourLight"];
                        break;
                    case EnabledLights.Six:
                        CurrentTechnique = (normalMapEnabled)
                            ? Techniques["AnimatedModel_SixLightWithNormal"]
                            : Techniques["AnimatedModel_SixLight"];
                        break;
                    case EnabledLights.Eight:
                        CurrentTechnique = (normalMapEnabled)
                            ? Techniques["AnimatedModel_EightLightWithNormal"]
                            : Techniques["AnimatedModel_EightLight"];
                        break;
                }
            }
            else
            {
                CurrentTechnique = Techniques["AnimatedModel_NoLight"];
            }
        }

        private ShaderProfile GetShaderProfile(string annotation)
        {
            return (ShaderProfile)Enum.Parse(typeof(ShaderProfile), annotation, true);
        }

        internal static SkinnedModelBasicEffect Read(ContentReader input)
        {
            IGraphicsDeviceService graphicsDeviceService = (IGraphicsDeviceService)input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService));
            GraphicsDevice graphicsDevice = graphicsDeviceService.GraphicsDevice;
            SkinnedModelBasicEffect basicEffect = new SkinnedModelBasicEffect(graphicsDevice, (EffectPool)null);
            basicEffect.material.EmissiveColor = input.ReadVector3();
            basicEffect.material.DiffuseColor = input.ReadVector3();
            basicEffect.material.SpecularColor = input.ReadVector3();
            basicEffect.material.SpecularPower = input.ReadSingle();
            basicEffect.DiffuseMapEnabled = input.ReadBoolean();
            basicEffect.NormalMapEnabled = input.ReadBoolean();
            basicEffect.SpecularMapEnabled = input.ReadBoolean();
            basicEffect.DiffuseMap = input.ReadObject<Texture2D>();
            basicEffect.NormalMap = input.ReadObject<Texture2D>();
            basicEffect.SpecularMap = input.ReadObject<Texture2D>();
            basicEffect.lightEnabled = false;
            basicEffect.enabledLights = EnabledLights.One;
            return basicEffect;
        }
    }
}